﻿using System;
using System.Net.Sockets;

namespace Yuki.Sockets
{
    public unsafe abstract class ClientSocket
    {
        private byte[] buffer;
        private Socket connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        protected IPacketCipher Crypto;
        private bool enabled;
        public SocketEvent<ClientSocket, object> OnConnect;
        public SocketEvent<ClientSocket, object> OnConnecting;
        public SocketEvent<ClientSocket, object> OnDisconnect;
        public SocketEvent<ClientSocket, SocketError> OnError;
        public SocketEvent<ClientSocket, byte[]> OnReceive;
        private string remoteip;
        private ushort remoteport;

        public ClientSocket()
        {
            this.BufferSize = 0xffff;
        }

        private void AsyncReceive(IAsyncResult res)
        {
            try
            {
                SocketError Error;
                YukiSocketClient Client = (YukiSocketClient) res.AsyncState;
                Client.RecvSize = Client.Connection.EndReceive(res, out Error);
                if ((Error == SocketError.Success) && (Client.RecvSize > 0))
                {
                    byte[] dest = new byte[Client.RecvSize];
                    if (this.Crypto != null)
                    {
                        this.Crypto.Decrypt(Client.Buffer, dest, dest.Length);
                    }
                    else
                    {
                        Native.memcpy(dest, Client.Buffer, Client.RecvSize);
                    }
                    if (this.OnReceive != null)
                    {
                        this.OnReceive(this, dest);
                    }
                    this.connection.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, new AsyncCallback(this.AsyncReceive), null);
                }
                else
                {
                    this.enabled = false;
                    if ((Error != SocketError.Success) && (this.OnError != null))
                    {
                        this.OnError(this, Error);
                    }
                    if (this.OnDisconnect != null)
                    {
                        this.OnDisconnect(this, null);
                    }
                }
            }
            catch (SocketException e)
            {
                if (this.OnError != null)
                {
                    this.OnError(this, e.SocketErrorCode);
                }
            }
            catch (ObjectDisposedException)
            {
            }
        }

        public void Disable()
        {
            if (this.enabled)
            {
                this.connection.Shutdown(SocketShutdown.Both);
                this.enabled = false;
                if (this.OnDisconnect != null)
                {
                    this.OnDisconnect(this, null);
                }
                this.connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
        }

        public void Enable()
        {
            if (!this.enabled)
            {
                try
                {
                    if (this.OnConnecting != null)
                    {
                        this.OnConnecting(this, null);
                    }
                    this.connection.Connect(this.remoteip, this.remoteport);
                    if (this.connection.Connected && (this.OnConnect != null))
                    {
                        this.OnConnect(this, null);
                    }
                    this.connection.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, new AsyncCallback(this.AsyncReceive), null);
                    this.enabled = true;
                }
                catch (SocketException e)
                {
                    if (this.OnError != null)
                    {
                        this.OnError(this, e.SocketErrorCode);
                    }
                }
                catch (ObjectDisposedException)
                {
                }
            }
        }

        private void enabledCheck(string Variable)
        {
            if (this.enabled)
            {
                throw new Exception("Cannot modify " + Variable + " while socket is enabled.");
            }
        }

        public void Send(byte[] Bytes)
        {
            if (this.enabled)
            {
                if (this.Crypto != null)
                {
                    byte[] dest = new byte[Bytes.Length];
                    this.Crypto.Encrypt(Bytes, dest, dest.Length);
                    this.connection.Send(dest);
                }
                else
                {
                    this.connection.Send(Bytes);
                }
            }
        }

        public int BufferSize
        {
            get
            {
                return this.buffer.Length;
            }
            set
            {
                this.enabledCheck("BufferSize");
                this.buffer = new byte[value];
            }
        }

        public Socket Connection
        {
            get
            {
                return this.connection;
            }
        }

        public bool Enabled
        {
            get
            {
                return this.enabled;
            }
        }

        public string RemoteIP
        {
            get
            {
                return this.remoteip;
            }
            set
            {
                this.enabledCheck("RemoteIP");
                this.remoteip = value;
            }
        }

        public ushort RemotePort
        {
            get
            {
                return this.remoteport;
            }
            set
            {
                this.enabledCheck("RemotePort");
                this.remoteport = value;
            }
        }
    }
}

